package dataccess

/*
   dao层，封装业务无关的mongo操作
*/
import (
	"context"
	"sync"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type FilterUpdate struct {
	Filter bson.D
	Update bson.D
}

type Mgo struct {
	Database *mongo.Database
	Cli      *mongo.Client
}

func GetInstanceMgo() *Mgo {
	return mongoIns
}

// 数据库单例
var (
	mongoIns  *Mgo
	mongoOnce sync.Once
)

// 构造数据库的实例
func NewMgo(mongoUri, database string, timeout time.Duration, maxPoolSize uint64) (*Mgo, error) {
	var (
		err error
		cli *mongo.Client
	)
	mongoOnce.Do(func() {
		if cli, err = setConnect(mongoUri, timeout, maxPoolSize); err != nil {
			err = errors.Wrap(err, "set connect to mongo fail")
		}

		mongoIns = &Mgo{
			Database: cli.Database(database),
			Cli:      cli,
		}
	})
	return mongoIns, err
}

// 连接设置
func setConnect(mongoUrl string, timeout time.Duration, maxPoolSize uint64) (*mongo.Client, error) {
	var (
		err error
		cli *mongo.Client
	)
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	// 连接池
	cli, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoUrl).SetMaxPoolSize(maxPoolSize))
	if err != nil {
		err = errors.Wrap(err, "set connect to mongo fail")
	}
	return cli, err
}

// 关闭连接
func (m *Mgo) MgoClose() {
	if err := m.Cli.Disconnect(context.TODO()); err != nil {
		logrus.Error(err.Error(), "disconnect to mongo fail")
	}
}

// InsertOne 插入单个文档
func (m *Mgo) InsertOne(collection string, value interface{}) (*mongo.InsertOneResult, error) {
	var (
		insertResult *mongo.InsertOneResult
		err          error
	)
	if insertResult, err = m.Database.Collection(collection).InsertOne(context.TODO(), value); err != nil {
		return insertResult, errors.Wrap(err, "InsertOne fail")
	}
	return insertResult, nil
}

// InsertMany 插入多个文档
func (m *Mgo) InsertMany(collection string, values []interface{}) (*mongo.InsertManyResult, error) {
	var (
		result *mongo.InsertManyResult
		err    error
	)
	if result, err = m.Database.Collection(collection).InsertMany(context.TODO(), values); err != nil {
		return result, errors.Wrap(err, "InsertMany fail")
	}
	return result, nil
}

// DeleteOne 删除单个文档
func (m *Mgo) DeleteOne(collection, key string, value interface{}) (int64, error) {
	var (
		err    error
		result *mongo.DeleteResult
	)
	filter := bson.D{{Key: key, Value: value}}
	if result, err = m.Database.Collection(collection).DeleteOne(context.TODO(), filter, nil); err != nil {
		return 0, errors.Wrap(err, "DeleteOne fail")
	}
	return result.DeletedCount, nil
}

// DeleteMany 删除多个文档
func (m *Mgo) DeleteMany(collection, key string, value interface{}) (int64, error) {
	var (
		err    error
		result *mongo.DeleteResult
	)
	filter := bson.D{{Key: key, Value: value}}

	if result, err = m.Database.Collection(collection).DeleteMany(context.TODO(), filter); err != nil {
		return 0, errors.Wrap(err, "DeleteMany fail")
	}
	return result.DeletedCount, nil
}

// UpdateOne 更新单个文档
func (m *Mgo) UpdateOne(collection string, filter, update interface{}) (*mongo.UpdateResult, error) {
	var (
		err    error
		result *mongo.UpdateResult
	)
	if result, err = m.Database.Collection(collection).UpdateOne(context.TODO(), filter, update); err != nil {
		return result, errors.Wrap(err, "UpdateOne fail")
	}
	return result, nil
}

// UpdateMany 更新多个文档
func (m *Mgo) UpdateMany(collection string, filter, update interface{}) (*mongo.UpdateResult, error) {
	var (
		err    error
		result *mongo.UpdateResult
	)
	if result, err = m.Database.Collection(collection).UpdateMany(context.TODO(), filter, update); err != nil {
		return result, errors.Wrap(err, "UpdateMany fail")
	}
	return result, nil
}

// FindOne 查询单个文档
func (m *Mgo) FindOne(collection, key string, value interface{}) *mongo.SingleResult {
	filter := bson.D{{Key: key, Value: value}}
	singleResult := m.Database.Collection(collection).FindOne(context.TODO(), filter)
	return singleResult
}

// FindMany 查询多个文档
func (m *Mgo) FindMany(collection string, filter interface{}) (*mongo.Cursor, error) {
	return m.Database.Collection(collection).Find(context.TODO(), filter)
}

// FindManyByFilters 多条件查询
func (m *Mgo) FindManyByFilters(collection string, filter interface{}) (*mongo.Cursor, error) {
	return m.Database.Collection(collection).Find(context.TODO(), filter)
}

// CollectionCount 查询集合里有多少数据
func (m *Mgo) CollectionCount(collection string) (string, int64) {
	name := m.Database.Collection(collection).Name()
	size, err := m.Database.Collection(collection).EstimatedDocumentCount(context.TODO())
	if err != nil {
		return "", size
	}
	return name, size
}

// GetCollectionDocs 按选项查询集合
// Skip 跳过
// Limit 读取数量
// sort 1 ，-1 . 1 为升序 ， -1 为降序
func (m *Mgo) GetCollectionDocs(collection string, Skip, Limit int64, sort int, filter bson.D) (*mongo.Cursor, error) {
	var (
		err    error
		result *mongo.Cursor
	)
	SORT := bson.D{{Key: "_id", Value: sort}}
	findOptions := options.Find().SetSort(SORT).SetLimit(Limit).SetSkip(Skip)
	if result, err = m.Database.Collection(collection).Find(context.Background(), filter, findOptions); err != nil {
		return result, errors.WithMessage(err, "get collection docs fail")
	}
	return result, nil
}

// BulkWrite 批量写入文档
func (m *Mgo) BulkWrite(collection string, filterUpdate []FilterUpdate) (*mongo.BulkWriteResult, error) {
	var (
		models []mongo.WriteModel
		model  mongo.WriteModel
	)
	opts := options.BulkWrite().SetOrdered(false)

	for _, v := range filterUpdate {
		model = mongo.NewUpdateOneModel().SetFilter(v.Filter).SetUpdate(v.Update).SetUpsert(true)
		models = append(models, model)
	}

	res, err := m.Database.Collection(collection).BulkWrite(context.TODO(), models, opts)
	return res, err
}
